<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 6.0.0">
  <link rel="apple-touch-icon" sizes="180x180" href="/wang-cheng/images/apple-touch-icon-next.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/wang-cheng/images/favicon-32x32-next.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/wang-cheng/images/favicon-16x16-next.png">
  <link rel="mask-icon" href="/wang-cheng/images/logo.svg" color="#222">

<link rel="stylesheet" href="/wang-cheng/css/main.css">


<link rel="stylesheet" href="/wang-cheng/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"cheng-wang123.gitee.io","root":"/wang-cheng/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="GC如何判断对象可以被回收 引用计数法:每个对象有一个引用计数属性，新增一个引用时计数加一，引用释放时计数减一，计数为0时可以回收(Java不常用)  可达性分析法:从GC Roots开始向下搜索，搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时，则证明此对象是不可用的，那么虚拟机就判断是可回收对象   1引用计数法，可能会出现A引用了B，B又引用了A，这时候就算他们都">
<meta property="og:type" content="article">
<meta property="og:title" content="面试题(三)">
<meta property="og:url" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/index.html">
<meta property="og:site_name" content="诚の博客">
<meta property="og:description" content="GC如何判断对象可以被回收 引用计数法:每个对象有一个引用计数属性，新增一个引用时计数加一，引用释放时计数减一，计数为0时可以回收(Java不常用)  可达性分析法:从GC Roots开始向下搜索，搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时，则证明此对象是不可用的，那么虚拟机就判断是可回收对象   1引用计数法，可能会出现A引用了B，B又引用了A，这时候就算他们都">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E6%B7%98%E6%B1%B0%E6%B5%81%E7%A8%8B.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/Java%E5%A0%86%E5%86%85%E5%AD%98.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B61.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B62.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/Java%E5%A0%86%E5%86%85%E5%AD%98.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/G1%E6%94%B6%E9%9B%86%E5%99%A8%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/G1%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/ThreadLocalMap.png">
<meta property="og:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%BC%B1%E5%BC%95%E7%94%A8.png">
<meta property="article:published_time" content="2022-03-19T05:57:41.000Z">
<meta property="article:modified_time" content="2022-05-11T08:43:34.542Z">
<meta property="article:author" content="王诚">
<meta property="article:tag" content="面试题">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E6%B7%98%E6%B1%B0%E6%B5%81%E7%A8%8B.png">

<link rel="canonical" href="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>面试题(三) | 诚の博客</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

<link rel="alternate" href="/wang-cheng/atom.xml" title="诚の博客" type="application/atom+xml">
</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/wang-cheng/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">诚の博客</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">记录学习中的点点滴滴</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/wang-cheng/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/wang-cheng/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/wang-cheng/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/wang-cheng/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="https://cheng-wang123.gitee.io/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/wang-cheng/images/avatar.gif">
      <meta itemprop="name" content="王诚">
      <meta itemprop="description" content="命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="诚の博客">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          面试题(三)
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-03-19 13:57:41" itemprop="dateCreated datePublished" datetime="2022-03-19T13:57:41+08:00">2022-03-19</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-05-11 16:43:34" itemprop="dateModified" datetime="2022-05-11T16:43:34+08:00">2022-05-11</time>
              </span>

          
            <span class="post-meta-item" title="阅读次数" id="busuanzi_container_page_pv" style="display: none;">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span id="busuanzi_value_page_pv"></span>
            </span>

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <h2 id="GC如何判断对象可以被回收"><a href="#GC如何判断对象可以被回收" class="headerlink" title="GC如何判断对象可以被回收"></a><strong>GC如何判断对象可以被回收</strong></h2><ul>
<li><p>引用计数法:每个对象有一个引用计数属性，新增一个引用时计数加一，引用释放时计数减一，计数为0时可以回收(Java不常用)</p>
</li>
<li><p>可达性分析法:从GC Roots开始向下搜索，搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时，则证明此对象是不可用的，那么虚拟机就判断是可回收对象</p>
</li>
</ul>
<figure class="highlight dns"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">引用计数法，可能会出现<span class="keyword">A</span>引用了B，B又引用了<span class="keyword">A</span>，这时候就算他们都不再使用了，但因为相互引用，计数器=<span class="number">1</span> 永远无法被回收。</span><br></pre></td></tr></table></figure>
<p>GC Roots的对象有：</p>
<ul>
<li><p>虚拟机栈(栈帧中的本地变量表)中引用的对象</p>
</li>
<li><p>方法区中类静态属性引用的对象</p>
</li>
<li><p>方法区中常量引用的对象</p>
</li>
<li><p>本地方法栈中JNI(即一般说的Native方法)引用的对象</p>
</li>
</ul>
<p>可达性算法中的不可达对象不是立即死亡的，对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链，第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalizer()方法。</p>
<p>当对象变成(GC Roots)不可达时，GC会判断该对象是否覆盖了finalize方法，若未覆盖，则直接将其回收。否则，若对象未执行过finalize方法，将其放入F-Queue队列，由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后，GC会再次判断该对象是否可达，若不可达，则进行回收，否则，对象“复活”。</p>
<p>每个对象只能触发一次finalize()方法</p>
<p>由于finalize()方法运行代码高昂，不确定性大，无法保证各个对象的调用顺序，不推荐大家使用，建议遗忘它。</p>
<h2 id="对象存活判定算法"><a href="#对象存活判定算法" class="headerlink" title="对象存活判定算法"></a><strong>对象存活判定算法</strong></h2><h3 id="引用计数法"><a href="#引用计数法" class="headerlink" title="引用计数法"></a><strong>引用计数法</strong></h3><ul>
<li><p>每个对象都包含一个<code>引用计数器</code>，用于存放引用计数(实际是存放被引用的次数)</p>
</li>
<li><p>每当有一个地方引用此对象时，引用计数+1</p>
</li>
<li><p>当引用失效(比如离开了局部变量的作用域或引用被设定为null)时，引用计数-1</p>
</li>
<li><p>当引用计数为0时，表示此对象不可能再被使用，因为这时我们已经没有任何方法可以得到此对象的引用了。</p>
</li>
</ul>
<h3 id="可达性分析"><a href="#可达性分析" class="headerlink" title="可达性分析"></a><strong>可达性分析</strong></h3><p>目前比较主流的编程语言(包括Java)，一般都会使用可达性分析算法来判断对象是否存活，它采用了类似于树结构的搜索机制。</p>
<p>首先每个对象引用都有机会称为树的根节点，可以被选定作为跟节点的条件如下：</p>
<ul>
<li><p>位于虚拟机栈的栈帧中的本地变量表中所引用到的对象(其实就是我们方法中的局部变量)同样也包括本地方法栈中JNI引用的对象</p>
</li>
<li><p>类的静态成员变量引用的对象</p>
</li>
<li><p>方法区中，常量池里面引用的对象，比如我们之前提到的String类型对象。</p>
</li>
<li><p>被添加了锁的对象(比如synchronized关键字)</p>
</li>
<li><p>虚拟机内部需要用到的对象</p>
</li>
</ul>
<h3 id="最终判定"><a href="#最终判定" class="headerlink" title="最终判定"></a><strong>最终判定</strong></h3><p>虽然在经历了可达性分析算法之后基本可以判定哪些对象能够被回收，但是并不代表此对象一定会被回收，我们依然可以在最终判定阶段对其进行挽留</p>
<p>在顶级父类Object中有一个finalize()方法，该方法就是最终判定。</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E6%B7%98%E6%B1%B0%E6%B5%81%E7%A8%8B.png" class title="淘汰流程">
<h2 id="垃圾回收算法"><a href="#垃圾回收算法" class="headerlink" title="垃圾回收算法"></a><strong>垃圾回收算法</strong></h2><h3 id="分代收集机制"><a href="#分代收集机制" class="headerlink" title="分代收集机制"></a><strong>分代收集机制</strong></h3><p>如果我们对堆中的每一个对象都依次判定是否需要回收，这样的效率其实是很低的，那么有没有更好的回收机制呢？第一步，我们可以对堆中的对象进行分代管理</p>
<p>比如某些对象，在多次垃圾回收时，都未被判定为可回收对象，我们完全可以将这一部分对象放在一起，并让垃圾收集器减少回收此区域对象的频率，这样就能更好的提高垃圾回收效率了</p>
<p>因此，Java虚拟机将堆内存划分为<code>新生代</code>、<code>老年代</code>和<code>永久代</code>(<code>永久代</code>其实就是方法区)，这里我们主要讨论<code>新生代</code>和<code>老年代</code></p>
<p>不同的分代内存回收机制也存在一些不同之处，在HotSpot虚拟机中，新生代被划分为三块，一块较大的Eden空间和两块较小的Survivor空间，默认比例为8：1：1，老年代的GC频率相对较低，永久代一般存放类信息等(其实就是方法区的实现)，如图所示：</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/Java%E5%A0%86%E5%86%85%E5%AD%98.png" class title="Java堆内存">
<p>首先，所有新创建的对象，都会进入新生代的Eden区(如果是大对象，会直接丢尽老年区),在进行新生代区域的垃圾回收时，首先会对新生代区域的对象扫描，并回收不再使用的对象:</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.png" class title="垃圾回收">
<p>接着，在依次垃圾回收之后，Eden区域没有被回收的对象，会进入Survivor区。在一开始From和To都是空的，而GC后，所有Eden区域存活的对象都会直接被放入到From区，最后From区和To发生一次交换，也就是说目前存放我们对象的From区，变为To区，而To区变为From区。</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B61.png" class title="垃圾回收">
<p>接着就是下一次垃圾回收了，操作与上面一样，不过这时由于我们From区域已经存在对象了，所以在Eden区的存活对象复制到From区之后，所有To区域中的对象会进行年龄判定(每经历一轮GC年龄+1，如果对象的年龄大于默认值15，那么会直接进入到老年代，否则移动到From区)</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B62.png" class title="垃圾回收">
<p>不断重复上面步骤</p>
<p>垃圾收集分为三个等级：</p>
<ol>
<li><p>Minor GC ：次要垃圾回收，主要进行新生代区域的垃圾收集</p>
<ul>
<li>触发条件：新生代的Eden区容量已满时</li>
</ul>
</li>
<li><p>Major GC :主要垃圾回收，主要进行老年代的垃圾收集</p>
</li>
<li><p>Full GC :完全垃圾回收，对整个Java堆内存和方法区进行垃圾回收</p>
<ul>
<li><p>触发条件1：每次晋升到老年代的对象平均大小大于老年代剩余空间</p>
</li>
<li><p>触发条件2：Minor GC后存活的对象超过了老年代剩余空间</p>
</li>
<li><p>触发条件3：永久代内存不足(JDK8之前)</p>
</li>
<li><p>触发条件4：手动调用System.gc()方法</p>
</li>
</ul>
</li>
</ol>
<h3 id="标记-清除算法"><a href="#标记-清除算法" class="headerlink" title="标记-清除算法"></a><strong>标记-清除算法</strong></h3><p>首先标记出所有需要回收的对象，然后再依次回收掉被标记的对象，或是标记出所有不需要回收的对象，只回收未标记的对象。实际上这种算法是非常基础的，并且最易于理解</p>
<p>虽然此方法非常简单，但缺点也非常明显，首先如果内存中存在大量的对象，那么可能就会存在大量的标记，并且大规模的进行清除。并且一次清除之后，连续的内存空间可能会出现许许多多的空隙，碎片化会导致连续内存空间利用率降低。</p>
<h3 id="标记-复制算法"><a href="#标记-复制算法" class="headerlink" title="标记-复制算法"></a><strong>标记-复制算法</strong></h3><p>既然标记清除算法在面对大量对象时效率很低，那么我们可以采用标记-复制算法。它将容量分为同样大小的两块区域</p>
<p>标记复制算法，实际上就是将内存区域划分为相同大小的两块区域，每次只使用其中的一块区域，每次垃圾回收结束后，将所有存活的对象全部复制到另一块区域中，并一次性清空当前区域。虽然浪费了一些时间进行复制操作，但是这样能够很好的解决对象大面积回收后空间碎片化严重的问题</p>
<p>这种算法非常适用于新生代(因为新生代的回收率极高，一般不会留下太多的对象)的垃圾回收，而我们之前所说的新生代Survivor区其实就是这个思路，包括8:1:1的比例也是为了对标记复制算法进行优化而采取的。</p>
<h3 id="标记-整理算法"><a href="#标记-整理算法" class="headerlink" title="标记-整理算法"></a><strong>标记-整理算法</strong></h3><p>虽然标记-复制算法能够很好的应对新生代高回收率的场景，但是放到老年代，它就显得很鸡肋了。我们知道，一般长期都回收不到的对象，才有机会进入到老年代，所以老年代一般都是些钉子户，可能一次GC后，仍然存留很多对象。而标记复制算法会在GC后完整复制整个区域内容，并且会折损50%的区域，这样显然不适用于老年代</p>
<p>那么我们能否这样，在标记所有待回收对象之后，不急着去进行回收操作，而是将所有待回收的对象整齐排列在一段内存空间中，而需要回收的对象全部往后丢，这样，前半部分的所有对象都是无需进行回收的，而后半部分直接一次性清除即可。</p>
<p>虽然这样能保证内存空间充分使用，并且也没有标记复制算法那么繁杂，但是缺点也是显而易见的，它的效率比前两者都低。甚至由于需要修改对象在内存中的位置，此时程序必须要暂停才可以，在极端情况下，可能会导致整个程序发生停顿</p>
<p>所以，我们可以将标记清除算法和标记整理算法混合使用，在内存空间还不是很凌乱的时候，采用标记清除算法其实是没有多大问题的，当内存空间凌乱到一定程度后，我们可以进行一次标记整理算法。</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/Java%E5%A0%86%E5%86%85%E5%AD%98.png" class title="Java堆内存">
<h2 id="常用垃圾回收器有哪些"><a href="#常用垃圾回收器有哪些" class="headerlink" title="常用垃圾回收器有哪些"></a><strong>常用垃圾回收器有哪些</strong></h2><h3 id="Serial收集器"><a href="#Serial收集器" class="headerlink" title="Serial收集器"></a><strong>Serial收集器</strong></h3><p>元老级收集器，在JDK1.3之前，是虚拟机新生代区域收集器的唯一选择。这时一款单线程的垃圾收集器，也就是说，当开始进行垃圾回收时，需要暂停所有线程，直到垃圾收集工作结束。它的新生代收集算法采用的是标记复制算法，老年代采用的是标记整理算法。</p>
<p>优点：</p>
<ol>
<li>设计简单而高效</li>
<li>在用户的桌面应用场景中，内存一般不大，可以在较短时间内完成垃圾收集，只要不频繁发生，使用串行回收器是可以接收的</li>
</ol>
<p>缺点：单线程，在进行垃圾回收时，需要暂停所有线程。</p>
<h3 id="ParNew收集器"><a href="#ParNew收集器" class="headerlink" title="ParNew收集器"></a><strong>ParNew收集器</strong></h3><p>相当于是Serial收集器的多线程版本，能够支持多线程垃圾收集</p>
<p>同样是先暂停所有的线程，然后再多线程去进行垃圾回收</p>
<h3 id="Parallel-Scavenge-Parallel-Old收集器"><a href="#Parallel-Scavenge-Parallel-Old收集器" class="headerlink" title="Parallel Scavenge/Parallel Old收集器"></a><strong>Parallel Scavenge/Parallel Old收集器</strong></h3><p>Parallel Scavenge同样是一款面向新生代的垃圾收集器，同样采用标记复制算法实现，在JDK6时也推出了其老年代收集器Parallel Old，采用标记整理算法实现</p>
<p>同样是先暂停所有的线程，然后再多线程去进行垃圾回收</p>
<p>与ParNew收集器不同的是，它会自动衡量一个吞吐量，并根据吞吐量来决定每次垃圾回收的时间，这种自适应机制，能够很好的权衡当前机器的性能，根据性能选择最优方案。</p>
<p>目前JDK8采用的就是这种Parallel Scavenge + Parallel Old的垃圾回收方案</p>
<h3 id="CMS收集器"><a href="#CMS收集器" class="headerlink" title="CMS收集器"></a><strong>CMS收集器</strong></h3><p>在JDK1.5,HotSpot推出了一款在强交互应用中几乎认为有划时代意义的垃圾收集器：CMS(Concurrent-Mark-Sweep)收集器，这款收集器是HotSpot虚拟机中第一款真正意义上的并发(注意这里的并发和之前的并行是有区别的，并发可理解为同时运行用户线程和GC线程，而并行可以理解为多条GC线程同时工作)收集器，它第一次实现了让垃圾收集线程与用户线程同时工作。</p>
<p>采用的是标记清除算法</p>
<p>它的垃圾回收分为4个阶段：</p>
<ul>
<li><p>初始标记(需要暂停用户线程)：这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象，速度比较快，不用担心会停顿太长时间</p>
</li>
<li><p>并发标记：从GC Roots的直接关联对象开始遍历整个对象图的过程，这个过程耗时较长但是不需要停顿用户线程，可以与垃圾收集线程一起并发运行</p>
</li>
<li><p>重新标记(需要暂停用户线程)：由于并发标记阶段可能某些用户线程会导致标记产生变动，因此这里需要再次暂停所有线程进行并发标记，这个时间会比初始标记时间长一点</p>
</li>
<li><p>并发清除：最后就可以直接将所有标记好的无用对象进行删除，因为这些对象程序中也用不到了，所以可以与用户线程并发运行。</p>
</li>
</ul>
<p>虽然它的有点非常之大，但是缺点也是显而易见的，我们之前说过，标记清除算法会产生大量的内存碎片，导致可用连续空间逐渐变少，长期这样下来，会有更高的概率触发Full GC，并且在与用户线程并发执行的情况下，也会占用一部分的系统资源，导致用户线程的运行速度一定程度上减慢</p>
<p>不过，如果你希望的是最低的GC停顿时间，这款垃圾收集器无疑是最佳选择，不过自从G1收集器问世之后，CMS收集器不再推荐使用</p>
<h3 id="Garbage-First-G1-收集器"><a href="#Garbage-First-G1-收集器" class="headerlink" title="Garbage First(G1)收集器"></a><strong>Garbage First(G1)收集器</strong></h3><p>此垃圾收集器也是一款划时代的垃圾收集器，在JDK7的时候正式走上历史舞台，它是一款主要面向于服务端的垃圾收集器，并且在JDK9时，取代了JDK8默认的Parallel Scavenge + Parallel Old的回收方案。</p>
<p>我们知道，我们的垃圾回收分为 Minor GC,Major GC 和 Full GC,他们分别对应的是新生代、老年代和整个堆内存的垃圾回收，而G1收集器巧妙地绕过了这些约定，它将整个Java堆划分为2048个大小相同的独立Region块，每个Region块的大小根据堆空间的实际大小而定，整体被控制在1MB到32MB之间，且都为2的N次幂，所有的Region大小相同，且在JVM的整个生命周期内不会发生改变。</p>
<p>那么分出这些Region有什么意义呢？每个Region都可以根据需要，自由决定扮演哪个角色(Eden、Survivor和老年代)，收集器会根据对应的角色采用不同的回收策略。此外，G1收集器还存在一个Humongous区域，它专门用于存放大对象(一般认为大小超过了Region容量的一半的对象为大对象)这样，新生代、老年代在物理上，不再是一个连续的内存区域，而是到处分布的。</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/G1%E6%94%B6%E9%9B%86%E5%99%A8%E5%86%85%E5%AD%98%E6%A8%A1%E5%9E%8B.png" class title="G1收集器内存模型">
<p>它的回收过程与CMS大体类似：</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/G1%E7%BA%BF%E7%A8%8B%E6%A8%A1%E5%9E%8B.png" class title="G1线程模型">
<p>分为以下四个步骤：</p>
<ul>
<li><p>初始标记(暂停用户线程)：仅仅只是标记一下GC Roots能直接关联到的对象，并且修改TAMS指针的值，让下一阶段用户线程并发运行时，能正确的在可用的Region中分配新对象。这个阶段需要停顿线程，但耗时很短，而且是借用进行Minor GC的时候同步完成的，所以G1收集器在这个阶段实际并没有额外的停顿。</p>
</li>
<li><p>并发标记：从GC Roots开始对堆中的对象进行可达性分析，递归扫描整个堆里的对象图，找出要回收的对象，这阶段耗时较长，但可与用户程序并发执行</p>
</li>
<li><p>最终标记(暂停用户线程)：对用户线程做一个短暂的暂停，用于处理并发标记阶段漏标的那部分对象</p>
</li>
<li><p>筛选回收：负责更新Region的统计数据，对各个Region的回收价值和成本进行排序，根据用户所期望的停顿时间来指定回收计划，可以自由选择任意多个Region构成回收集，然后把决定回收的那一部分Region的存活对象复制到空的Region中，再清理掉整个旧Region的全部空间。这里的操作设计存活对象的移动，是必须暂停用户线程，由多个收集器线程并行完成的。</p>
</li>
</ul>
<p><strong>G1（Garbage first ）</strong></p>
<p>垃圾收集器是目前垃圾收集器理论发展的最前沿成果，相比与 CMS 收集器，G1 收集器两个最突出的改进是：</p>
<ol>
<li>基于标记-整理算法，不产生内存碎片。</li>
<li>可以非常精确控制停顿时间，在不牺牲吞吐量前提下，实现低停顿垃圾回收。</li>
</ol>
<p>G1 收集器避免全区域垃圾收集，它把堆内存划分为大小固定的几个独立区域，并且跟踪这些区域的垃圾收集进度，同时在后台维护一个优先级列表，每次根据所允许的收集时间，优先回收垃圾最多的区域。区域划分和优先级区域回收机制，确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。</p>
<h2 id="线程生命周期，线程有哪些状态"><a href="#线程生命周期，线程有哪些状态" class="headerlink" title="线程生命周期，线程有哪些状态"></a><strong>线程生命周期，线程有哪些状态</strong></h2><ol>
<li>线程通常有5中状态，创建、就绪、运行、阻塞和死亡</li>
<li><p>阻塞的情况又分为三种</p>
<ol>
<li>等待阻塞：运行的线程执行wait方法，该线程会释放占用的所有资源，JVM会把该线程放入”等待池”中。进入这个状态后，是不能自动唤醒的，必须依靠其他线程调用notify或notifyAll方法才能被唤醒，wait是object类的方法</li>
<li>同步阻塞：运行的线程在获取对象的同步锁时(竞争锁)，若该同步锁被别的线程占用(没有抢到锁)，则JVM会把线程放入”锁池”中。</li>
<li>其他阻塞：运行的线程执行sleep或join方法，或者发出了I/O请求，JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完成时，线程重新转入就绪状态。sleep是Thread类中的方法，wait是object类中的方法。</li>
</ol>
</li>
<li><p>新建状态(New)：创建了一个线程对象</p>
</li>
<li><p>就绪状态(Runnable)：线程对象创建后，其他线程调用该对象的start方法。该状态的线程位于可运行线程池中，变得可运行，等待获取CPU的使用权</p>
</li>
<li><p>运行状态(Running)：就绪状态的线程获取了CPU，执行程序代码</p>
</li>
<li><p>阻塞状态(Blocked)：阻塞状态是线程因为某种原因放弃CPU使用权，暂时停止运行。直到线程进入就绪状态，才有机会转到运行状态。</p>
</li>
<li><p>死亡状态(Dead)：线程执行完了或者因异常退出了run方法，该线程结束生命周期。</p>
</li>
</ol>
<h2 id="sleep-、wait-、join-、yield-的区别"><a href="#sleep-、wait-、join-、yield-的区别" class="headerlink" title="sleep()、wait()、join()、yield()的区别"></a><strong>sleep()、wait()、join()、yield()的区别</strong></h2><p>1、锁池：所有需要竞争同步锁的线程都会放在锁池当中，比如当前对象的锁已经被其中一个线程得到，则其他线程需要在这个锁池进行等待，当前面的线程释放同步锁后锁池中的线程去竞争同步锁，当某个线程得到后会进入就绪队列进行等待CPU资源分配。</p>
<p>2、等待池(主要针对wait方法)：当我们调用(能调用方法，说明此时这个线程应该是拿到锁的一个状态)wait()后，线程会放到等待池中，等待池中的线程是不会去竞争同步锁(因为wait方法将这个锁给释放掉了，不允许他们再去竞争锁了)。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁，notify()是随机从等待池选出一个线程放到锁池，而notifyAll()是将等待池中的所有线程放到锁池中。</p>
<ol>
<li><p>sleep是Thread类的静态本地方法，wait则是Object类的本地方法</p>
</li>
<li><p>sleep方法不会释放lock，但是wait会释放，而且会加入到等待池。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sleep</span>就是把cpu的执行资格和执行权释放出去，不再运行此线程，当定时时间结束再取回cpu资源，参与cpu调度，获取到cpu资源后就可以继续运行了。而如果调<span class="built_in">sleep</span>时该线程有锁(调用<span class="built_in">sleep</span>的代码块有synchronized修饰)，那么<span class="built_in">sleep</span>不会释放这个锁，而是把锁带着进入冻结状态，也就是说其他需要这个锁的线程不可能拿到这个锁，也就是说这个程序无法执行。如果在睡眠期间其他线程调用了这个线程的interrupt方法，那么这么线程会抛出interrupteexception异常返回，这点和<span class="built_in">wait</span>是一样的。</span><br></pre></td></tr></table></figure>
</li>
<li><p>sleep方法不依赖于同步器synchronized(没有被synchronized修饰的代码块中也可以使用sleep)，但是wait需要依赖synchronized关键字(两者必须配套使用)。</p>
</li>
<li><p>sleep不需要被唤醒(休眠之后退出阻塞)，但是wait需要(不指定时间需要被别人中断，如果带上时间参数，也是不需要被唤醒的)</p>
</li>
<li><p>sleep一般用于当前线程休眠，或者轮循暂停操作，wait则多用于多线程之间的通信。</p>
</li>
<li><p>sleep会让出cpu时间，且强制上下文切换，而wait则不一定会让出cpu，wait后可能还是有机会重新竞争带锁继续执行的(wait后如果重新竞争到了锁，那么是不需要进行上下文切换的)</p>
</li>
</ol>
<p>yield()执行后线程直接进入就绪状态(就绪状态而不是阻塞状态，这说明该线程可以继续竞争锁)，马上释放了cpu的执行权，但是依然保留了cpu的执行资格，所以有可能cpu下次进行线程调用还会让这个线程获取到执行权继续执行(也就是说yield啥事也没干，继续往下执行)</p>
<p>join()执行后线程进入阻塞状态，例如在线程B中调用线程A的join()，那么线程B会进入阻塞状态，直到线程A结束或中断线程(注意是中断线程，如果阻塞的话是需要继续等待阻塞完成的)<br><figure class="highlight csharp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span>(<span class="params">String[] args</span>) throws InterruptedException</span>&#123;</span><br><span class="line">    Thread t1 = <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable()&#123;</span><br><span class="line">        @Override</span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span>()</span>&#123;</span><br><span class="line">            <span class="keyword">try</span>&#123;</span><br><span class="line">                Thread.sleep(<span class="number">3000</span>);</span><br><span class="line">            &#125; <span class="keyword">catch</span> (InterruptedException e)&#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">            System.<span class="keyword">out</span>.println(<span class="string">&quot;t1线程&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    t1.start();</span><br><span class="line">    t1.<span class="keyword">join</span>();</span><br><span class="line">    <span class="comment">//这行代码必须要等t1全部执行完毕，才会执行</span></span><br><span class="line">    System.<span class="keyword">out</span>.println(<span class="string">&quot;main线程&quot;</span>);</span><br><span class="line">&#125;</span><br><span class="line">ti线程</span><br><span class="line">main线程</span><br></pre></td></tr></table></figure></p>
<h2 id="对线程安全的理解"><a href="#对线程安全的理解" class="headerlink" title="对线程安全的理解"></a><strong>对线程安全的理解</strong></h2><p>讲线程安全，实际上指的不是线程安全，应该是内存安全，堆是共享内存，可以被所有线程访问<br><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">当多个线程访问同一个对象<span class="comment">(对象是处在堆中的)</span>，如果不用进行额外的同步控制或其他的协调操作，调用这个对象的行为都可以获得正确的结果，我们就说这个对象是线程安全的。</span><br></pre></td></tr></table></figure></p>
<p><strong>堆</strong>是进程和线程(所有线程共享同一个堆)共有的空间，分全局堆和局部堆。全局堆就是所有没有分配的空间，局部堆就是用户分配的空间(已经被用户分配了的空间)。堆在操作系统对进程初始化的时候分配，运行过程中也可以向系统要额外的堆，但是用完了要还给操作系统，要不然就是内存泄漏。<br><figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">在java中，堆是java虚拟机所管理的内存中最大的一块，是所有线程共享的一块内存区域，在虚拟机启动的时候创建。堆所存在的内存区域的唯一目的就是存放对象实例，几乎所有的对象实例以及数组都在这里分配内存。</span><br></pre></td></tr></table></figure></p>
<p><strong>栈</strong>是每个线程独有的，保存其运行状态和局部变量。栈在线程开始的时候初始化，每个线程的栈互相独立，因此，栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言中显示得分配和释放。</p>
<p>目前主流操作系统都是多任务的，即多个进程同时运行。为了保证安全，每个进程只能访问分配给自己的内存空间，而不能访问别的进程的，这是由操作系统保障的。</p>
<p>在每个进程的内存空间中都会有一块特殊的公共区域，通常称为堆(内存)。进程内的所有线程都可以访问到该区域，这就是造成问题的潜在原因。</p>
<h2 id="Thread、Runnable的区别"><a href="#Thread、Runnable的区别" class="headerlink" title="Thread、Runnable的区别"></a><strong>Thread、Runnable的区别</strong></h2><p>Thread和Runnable的实质是继承关系，没有可比性。Thread实现了Runnable接口。无论使用Runnable还是Thread，都会new Thread，然后执行run方法。用法上，如果有复杂的线程操作需求，那就选择继承Thread，如果只是简单的执行一个任务，那就实现Runnable。</p>
<figure class="highlight haxe"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//会卖出多一倍的票</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test</span></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> void main(<span class="keyword">String</span>[] args)&#123;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">new</span> <span class="type">MyThread</span>().start;</span><br><span class="line">        <span class="keyword">new</span> <span class="type">MyThread</span>().start;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">MyThread</span> <span class="keyword"><span class="keyword">extends</span> <span class="type">Thread</span></span></span>&#123;</span><br><span class="line">        <span class="keyword">private</span> int ticket = <span class="number">5</span>;</span><br><span class="line">        <span class="keyword">public</span> void run()&#123;</span><br><span class="line">            <span class="keyword">while</span>(<span class="literal">true</span>)&#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;Thread ticket = &quot;</span> + ticket--);</span><br><span class="line">                <span class="keyword">if</span>(ticket &lt; <span class="number">0</span>)&#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//正常卖出</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Test2</span></span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> void main(<span class="keyword">String</span>[] args)&#123;</span><br><span class="line"></span><br><span class="line">        MyThread2 mt = <span class="keyword">new</span> <span class="type">MyThread2</span>();</span><br><span class="line">        <span class="keyword">new</span> <span class="type">Thread</span>(mt).start;</span><br><span class="line">        <span class="keyword">new</span> <span class="type">Thread</span>(mt).start;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">MyThread2</span> <span class="keyword"><span class="keyword">implements</span> <span class="type">Runnable</span></span></span>&#123;</span><br><span class="line">        <span class="keyword">private</span> int ticket = <span class="number">5</span>;</span><br><span class="line">        <span class="keyword">public</span> void run&#123;</span><br><span class="line">            <span class="keyword">while</span>(<span class="literal">true</span>)&#123;</span><br><span class="line">                System.out.println(<span class="string">&quot;Runnable ticket = &quot;</span> + ticket--);</span><br><span class="line">                <span class="keyword">if</span>(ticket &lt; <span class="number">0</span>)&#123;</span><br><span class="line">                    <span class="keyword">break</span>;</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>原因是第一个例子new了两个实例，启动了两个线程，而MyThread的ticket是成员变量，相当于有两个ticket，所以肯定会打印两遍ticket。而第二个只new了一个实例，启动两个线程，这两个线程共享一个ticket，所以只会打印一遍。</p>
<h2 id="对守护线程的理解"><a href="#对守护线程的理解" class="headerlink" title="对守护线程的理解"></a><strong>对守护线程的理解</strong></h2><p>守护线程：为所有非守护线程提供服务的线程(Java中只有两种线程，守护线程、用户线程);任何一个守护线程都是整个JVM中所有非守护线程的保姆。</p>
<p>守护线程类似于整个进程的一个默默无闻的人;它的生死无关重要，它却依赖整个进程而运行，哪天其他线程结束了，没有要执行的了，程序就结束了，理都没理守护线程，就把它中断了。</p>
<p>注意：由于守护线程的终止是自身无法控制的，因此千万不要把IO、File等重要操作逻辑分配给它，因为它不靠谱。</p>
<p>守护线程的作用：</p>
<p>举例，GC垃圾回收线程：就是一个经典的守护线程，当我们的程序中不再有任何运行的Thread，程序就不会再产生垃圾，垃圾回收器也就无事可做，所以当垃圾回收线程是JVM上仅剩的线程时，垃圾回收线程会自动离开。它始终在低级别的状态中运行，用于实时监控和管理系统中的可回收资源。</p>
<p>应用场景：(1) 来为其他线程提供一些服务支持 (2) 或者在任何情况下，程序结束时，这个线程必须正常且立刻关闭，就可以作为守护线程来使用，反之，如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果，那么这个线程就不能是守护线程，而是用户线程。通常都是些关键的事务，比如说，数据库的录入或者更新，这些操作都是不可中断的。</p>
<p>使用方法：</p>
<p>thread.setDaemon(true)必须在thread.start()之前设置，否则会报错IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。</p>
<p>在Daemon线程中产生的新线程也是Daemon的。</p>
<p>守护线程不能去访问固有资源，比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。</p>
<p>Java自带的多线程框架，比如ExecutorService，会将守护线程转换为用户线程(也就是说所有线程都会成为用户线程)，所以如果要使用后台线程就不能用Java的线程池。</p>
<h2 id="ThreadLocal的原理和使用场景"><a href="#ThreadLocal的原理和使用场景" class="headerlink" title="ThreadLocal的原理和使用场景"></a><strong>ThreadLocal的原理和使用场景</strong></h2><p>每一个Thread对象均包含有一个ThreadLocalMap(key是ThreadLocal，value是要存的值)类型的成员变量threadLocals，它存储本线程中所有ThreadLocal对象以及对应的值。</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/ThreadLocalMap.png" class title="ThreadLocalMap">
<p>ThreadLocalMap由一个个Entry对象构成</p>
<p>Entry继承自WeakReference<ThreadLocal<?>&gt;，一个Entry由ThreadLocal对象和Object构成。由此可见，Entry的key是ThreadLocal对象，并且是一个弱引用。当没有指向key的强引用之后，该key就会被垃圾回收器回收</ThreadLocal<?></p>
<p>(ThreadLocal的使用)当执行set方法后，ThreadLocal首先会获取当前线程对象(Thread)，然后获取当前线程中ThreadLocalMap对象。再以当前ThreadLocal对象为key，将值存储进ThreadLocalMap(它是属于当前线程的)对象中。</p>
<p>get方法类似。ThreadLocal首先会获取当前线程对象，然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key，获取对应的value。</p>
<p>由于每一条线程都含有一个<strong>私有</strong>的ThreadLocalMap，这些容器相互独立互不影响，因此不会存在线程安全问题，从而也无需使用同步机制来保证多条线程访问容器的互斥性。</p>
<p>使用场景：</p>
<ol>
<li><p>在进行对象跨层传递的时候，使用ThreadLocal可以避免多次传递，打破层次间的约束(例如controller层与service层、dao层之间传递多个参数，如果不使用ThreadLocal的话，可能每一层都需要在方法上定义参数去传递，而使用ThreadLocal后，因为ThreadLocal是属于这个线程的，无论程序运行到哪里都是这一个线程，只需要在Controller中存储这个参数，然后可以在程序执行的任何地方取出这个参数进行使用(个人理解))</p>
</li>
<li><p>线程间数据隔离(ThreadLocal是属于线程私有，只有当前线程能够访问到，其他线程访问不到，起到数据隔离效果)</p>
</li>
<li><p>进行事务操作，用于存储线程事务信息</p>
</li>
<li><p>数据库连接，Session会话管理</p>
</li>
</ol>
<figure class="highlight pgsql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Spring框架在事务开始时会给当前线程绑定一个Jdbc <span class="keyword">Connection</span>，在整个事务过程都是使用该线程绑定的<span class="keyword">Connection</span>来执行数据库操作，实现了事务的隔离性。Spring框架里面就是用的ThreadLocal来实现这种隔离</span><br></pre></td></tr></table></figure>
<h2 id="ThreadLocal内存泄漏的原因，如何避免"><a href="#ThreadLocal内存泄漏的原因，如何避免" class="headerlink" title="ThreadLocal内存泄漏的原因，如何避免"></a><strong>ThreadLocal内存泄漏的原因，如何避免</strong></h2><p>内存泄漏为程序在申请内存后，无法释放已申请的内存空间，一次内存泄漏危害可以忽略，但内存泄漏堆积后果很严重，无论多少内存，迟早会被占光。</p>
<p>不再会被使用的对象或变量占用的内存不能被回收，就是内存泄漏(内存不够是OOM，多次的内存泄漏，最终会导致OOM)</p>
<p>强引用:使用最普通的引用(New)(反射)，一个对象具有强引用，不会被垃圾回收器回收。当内存空间不足，Java虚拟机宁愿抛出OutOfMemoryError错误，使程序异常终止，也不回收这种对象。</p>
<p>如果想取消强引用和某个对象的关联，可以显式地将引用赋值为null，这样可以使JVM在合适地时间就会回收该对象。</p>
<p>弱引用:JVM进行垃圾回收时，无论内存是否充足，都会回收弱引用关联的对象。在Java中，用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用(缓存中使用弱引用，被回收也没关系)</p>
<p>ThreadLocal的实现原理，每个Thread维护一个ThreadLocalMap，key为使用弱引用的ThreadLocal实例，value为线程变量的副本</p>
<img src="/wang-cheng/2022/03/19/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%B8%89/%E5%BC%B1%E5%BC%95%E7%94%A8.png" class title="弱引用">
<p>(实线是强引用，虚线是弱引用)</p>
<p>ThreadLocalMap使用ThreadLocal的弱引用作为key，如果一个ThreadLocal不存在外部强引用时，Key(ThreadLocal)势必会被GC回收，这样就会导致ThreadLocalMap中key为null，而value还存在强引用，只有Thread线程退出之后，value的强引用链才会断，但如果当前线程再迟迟不结束的话，这些key为null的Entry的value就会一直存在一条强引用链(红色链条)(这个value就一直回收不掉，就会产生内存泄漏)</p>
<p>key使用强引用</p>
<p>当ThreadLocalMap的key为强引用回收ThreadLocal时，因为ThreadLocalMap还持有ThreadLocal的强引用，如果没有手动删除，ThreadLocal不会被回收，导致Entry内存泄漏</p>
<p>key使用弱引用</p>
<p>当ThreadLocalMap的key为弱引用回收ThreadLocal时，由于ThreadLocalMap持有ThreadLocal的弱引用，即使没有手动删除，ThreadLocal也会被回收。当key为null时，在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。</p>
<p>因此，ThreadLocalMap内存泄漏的根源是：由于ThreadLocalMap的生命周期跟Thread一样长，如果没有手动删除对应key就会导致内存泄漏，而不是因为弱引用</p>
<p>ThreadLocal正确的使用方法：</p>
<ul>
<li><p>每次使用完ThreadLocal都调用它的remove()方法清楚数据</p>
</li>
<li><p>将ThreadLocal变量定义成private static，这样就一直存在ThreadLocal的强引用，也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值，进而清除掉。</p>
</li>
</ul>

    </div>

    
    
    

      <footer class="post-footer">
          <div class="post-tags">
              <a href="/wang-cheng/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/" rel="tag"># 面试题</a>
          </div>

        


        
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/03/18/%E9%9D%A2%E8%AF%95%E9%A2%98-%E4%BA%8C/" rel="prev" title="面试题(二)">
      <i class="fa fa-chevron-left"></i> 面试题(二)
    </a></div>
      <div class="post-nav-item">
    <a href="/wang-cheng/2022/03/20/%E9%9D%A2%E8%AF%95%E9%A2%98-%E5%9B%9B/" rel="next" title="面试题(四)">
      面试题(四) <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#GC%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E5%AF%B9%E8%B1%A1%E5%8F%AF%E4%BB%A5%E8%A2%AB%E5%9B%9E%E6%94%B6"><span class="nav-number">1.</span> <span class="nav-text">GC如何判断对象可以被回收</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AF%B9%E8%B1%A1%E5%AD%98%E6%B4%BB%E5%88%A4%E5%AE%9A%E7%AE%97%E6%B3%95"><span class="nav-number">2.</span> <span class="nav-text">对象存活判定算法</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0%E6%B3%95"><span class="nav-number">2.1.</span> <span class="nav-text">引用计数法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%8F%AF%E8%BE%BE%E6%80%A7%E5%88%86%E6%9E%90"><span class="nav-number">2.2.</span> <span class="nav-text">可达性分析</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%9C%80%E7%BB%88%E5%88%A4%E5%AE%9A"><span class="nav-number">2.3.</span> <span class="nav-text">最终判定</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E7%AE%97%E6%B3%95"><span class="nav-number">3.</span> <span class="nav-text">垃圾回收算法</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#%E5%88%86%E4%BB%A3%E6%94%B6%E9%9B%86%E6%9C%BA%E5%88%B6"><span class="nav-number">3.1.</span> <span class="nav-text">分代收集机制</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%A0%87%E8%AE%B0-%E6%B8%85%E9%99%A4%E7%AE%97%E6%B3%95"><span class="nav-number">3.2.</span> <span class="nav-text">标记-清除算法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%A0%87%E8%AE%B0-%E5%A4%8D%E5%88%B6%E7%AE%97%E6%B3%95"><span class="nav-number">3.3.</span> <span class="nav-text">标记-复制算法</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#%E6%A0%87%E8%AE%B0-%E6%95%B4%E7%90%86%E7%AE%97%E6%B3%95"><span class="nav-number">3.4.</span> <span class="nav-text">标记-整理算法</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%B8%B8%E7%94%A8%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6%E5%99%A8%E6%9C%89%E5%93%AA%E4%BA%9B"><span class="nav-number">4.</span> <span class="nav-text">常用垃圾回收器有哪些</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Serial%E6%94%B6%E9%9B%86%E5%99%A8"><span class="nav-number">4.1.</span> <span class="nav-text">Serial收集器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#ParNew%E6%94%B6%E9%9B%86%E5%99%A8"><span class="nav-number">4.2.</span> <span class="nav-text">ParNew收集器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Parallel-Scavenge-Parallel-Old%E6%94%B6%E9%9B%86%E5%99%A8"><span class="nav-number">4.3.</span> <span class="nav-text">Parallel Scavenge&#x2F;Parallel Old收集器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#CMS%E6%94%B6%E9%9B%86%E5%99%A8"><span class="nav-number">4.4.</span> <span class="nav-text">CMS收集器</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#Garbage-First-G1-%E6%94%B6%E9%9B%86%E5%99%A8"><span class="nav-number">4.5.</span> <span class="nav-text">Garbage First(G1)收集器</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E7%BA%BF%E7%A8%8B%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%EF%BC%8C%E7%BA%BF%E7%A8%8B%E6%9C%89%E5%93%AA%E4%BA%9B%E7%8A%B6%E6%80%81"><span class="nav-number">5.</span> <span class="nav-text">线程生命周期，线程有哪些状态</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#sleep-%E3%80%81wait-%E3%80%81join-%E3%80%81yield-%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="nav-number">6.</span> <span class="nav-text">sleep()、wait()、join()、yield()的区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AF%B9%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E7%9A%84%E7%90%86%E8%A7%A3"><span class="nav-number">7.</span> <span class="nav-text">对线程安全的理解</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Thread%E3%80%81Runnable%E7%9A%84%E5%8C%BA%E5%88%AB"><span class="nav-number">8.</span> <span class="nav-text">Thread、Runnable的区别</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#%E5%AF%B9%E5%AE%88%E6%8A%A4%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%90%86%E8%A7%A3"><span class="nav-number">9.</span> <span class="nav-text">对守护线程的理解</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#ThreadLocal%E7%9A%84%E5%8E%9F%E7%90%86%E5%92%8C%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF"><span class="nav-number">10.</span> <span class="nav-text">ThreadLocal的原理和使用场景</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#ThreadLocal%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E7%9A%84%E5%8E%9F%E5%9B%A0%EF%BC%8C%E5%A6%82%E4%BD%95%E9%81%BF%E5%85%8D"><span class="nav-number">11.</span> <span class="nav-text">ThreadLocal内存泄漏的原因，如何避免</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
  <p class="site-author-name" itemprop="name">王诚</p>
  <div class="site-description" itemprop="description">命运对勇士低语，你无法抵御风暴；勇士低声回应，我就是风暴</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/wang-cheng/archives/">
        
          <span class="site-state-item-count">32</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/wang-cheng/tags/">
          
        <span class="site-state-item-count">12</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">王诚</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Gemini</a> 强力驱动
  </div>

        
<div class="busuanzi-count">
  <script async src="https://busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
    <span class="post-meta-item" id="busuanzi_container_site_uv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-user"></i>
      </span>
      <span class="site-uv" title="总访客量">
        <span id="busuanzi_value_site_uv"></span>
      </span>
    </span>
    <span class="post-meta-divider">|</span>
    <span class="post-meta-item" id="busuanzi_container_site_pv" style="display: none;">
      <span class="post-meta-item-icon">
        <i class="fa fa-eye"></i>
      </span>
      <span class="site-pv" title="总访问量">
        <span id="busuanzi_value_site_pv"></span>
      </span>
    </span>
</div>








      </div>
    </footer>
  </div>

  
  <script src="/wang-cheng/lib/anime.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.min.js"></script>
  <script src="/wang-cheng/lib/velocity/velocity.ui.min.js"></script>

<script src="/wang-cheng/js/utils.js"></script>

<script src="/wang-cheng/js/motion.js"></script>


<script src="/wang-cheng/js/schemes/pisces.js"></script>


<script src="/wang-cheng/js/next-boot.js"></script>




  















  

  

  

</body>
</html>
